package com.csair.irp.job.file.task;

import com.csair.irp.job.common.spring.SpringContext;
import com.csair.irp.job.file.parser.base.BaseFileParser;
import com.csair.irp.job.file.parser.base.ParseResult;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.time.LocalDate;
import java.time.LocalTime;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import static com.csair.irp.job.file.parser.base.BaseFileParser.TASK_START_TIME_KEY;

/**
 * 扫描文件存储路径
 *
 * @author liaolonglong
 */
@Component
public final class FileTask implements DisposableBean {

    private static final Logger log = LoggerFactory.getLogger(FileTask.class);

    /**
     * 任务开始执行时间只含小时和分钟，格式为：{hh}:{mm}，如：22:30,10:30
     */
    @Value("${datafile.task.begin-time:10:30}")
    private String beginTime;

    @Value("${datafile.task.days:15}")
    private int days;

    @Autowired
    private DispatchLogService dispatchLogService;

    @Autowired
    private FileBaseinfoService fileBaseinfoService;

    private static final int CORE_POOL_SIZE = Runtime.getRuntime().availableProcessors();

    private static final ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE, 2 * CORE_POOL_SIZE,
            60L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(1000), r -> new Thread(r, "数据文件调度线程"), (r, executor) -> {
        log.info("执行超出线程池拒绝的任务{}", r);
        r.run();
    });

    static {
        threadPoolExecutor.allowCoreThreadTimeOut(true);
    }

    /**
     * 记录正在执行的文件
     */
    private static final CopyOnWriteArrayList<String> HANDLING_FILE = new CopyOnWriteArrayList<>();

    /**
     * 扫描文件目录，把数据文件信息入库，并且需要解析的文件调用解析
     * <p>
     * 1.由主任务调度调用(需传入文件存储路径)，不同数据文件类型路径格式说明
     * {
     * 银行文件:[IRP_DATA/BANK_DATA/2018/12/12/]
     * 手动网关文件:[IRP_DATA/GATEWAY_DATA/2018/12/13/]
     * 自动网关文件:[IRP_DATA/GATEWAY_AUTOFILE/2018/12/14/]
     * 手工单文件:[IRP_DATA/MANUAL_DATA/2018/12/12/]
     * ET文件:[IRP_DATA/ET_DATA/2018/12/13/AET/,IRP_DATA/ET_DATA/2018/12/13/TCN/]
     * ATK文件:[IRP_DATA/ATK_DATA/2018/12/13/TCN/,RP_DATA/ATK_DATA/2018/12/13/ATK/]
     * 网关中间表:[IRP_DATA/GATEWAY_BANK_DATA/2018/10/31/],暂时不做
     * }
     * <p>
     * 2.根据文件路径通过t_file_baseinfo找到需要执行的调度日志列表     {有数据:goto3,}
     * 调度日志执行状态{0(未解析):扫描该目录,1(解析成功):不扫描该目录,2(解析失败):不扫描该目录,4(未推送):扫描该目录}
     * <p>
     * 3.并扫描
     * {
     * 手动上传类文件(银行文件、手动网关文件、手工单文件):(t_dispatch_log->t_file_baseinfo->t_file_manage)查数据文件表t_file_manage表获取要文件信息并解析(文件解析状态为0的记录)
     * 自动网关文件:生成带日期的文件目录，把存在的文件信息入库并解析
     * ET文件和ATK文件:生成带日期的文件目录，把存在的文件信息入库并解析
     * }
     * <p>
     * <p>
     * 最后都要执行的操作:更新任务调度时间，更新调度任务状态
     *
     * @param path 扫描文件目录,ATK和AET国内和国际目录,eg:GATEWAY_AUTOFILE/${date}，ET_DATA/${date}/AET;
     */
    public void start(String path) {

        //没到开始解析时间不用去解析
        //该步骤有问题，开始时间应配置在数据库中，然后通过读取数据判断什么时间开始执行任务，不到指定时间直接跳过
//        if (!isStartTask()) {
//            return;
//        }
//
        TaskType taskType = TaskType.getTaskTypeByPath(path);

        //查询需要解析调度任务
        LocalDate now = LocalDate.now();
        DispatchLogPO.StateEnum[] states = {DispatchLogPO.StateEnum.ETL_STATE_NO_RUN, DispatchLogPO.StateEnum.ETL_STATE_FILE_NOT_FOUND};
//        List<DispatchLogPO> needExecuteDispatchLogs = dispatchLogService.listNeedExecute(taskType, now.minusDays(days), now, states);
        List<DispatchLogPO> needExecuteDispatchLogs = dispatchLogService.listNeedExecute(taskType, now.minusDays(18), now.minusDays(18), states);

        if (needExecuteDispatchLogs.isEmpty()) {
            //如果没有未执行的调度任务就直接返回
            return;
        }

        //清空上一次任务缓存的执行文件
        HANDLING_FILE.clear();

        //遍历调度日志执行任务
        needExecuteDispatchLogs.forEach(dispatchLog -> executeTask(dispatchLog, taskType, path));
    }

    /**
     * 执行任务
     *
     * @param dispatchLog
     * @param taskType
     * @param path
     */
    private void executeTask(DispatchLogPO dispatchLog, TaskType taskType, String path) {
        long id = dispatchLog.getId();

        //获取文件解析器
        BaseFileParser fileParser;
        FileBaseinfoPO fileBaseinfo = null;
        if (taskType != null) {
            fileParser = SpringContext.getBean(taskType.getBeanId());
        } else {
            //手动网关、银行、手工单
            fileBaseinfo = fileBaseinfoService.getById(dispatchLog.getDataResourceTypeId());
            if (fileBaseinfo != null) {
                fileParser = SpringContext.getBean(fileBaseinfo.getBeanId());
            } else {
                log.error("请核查数据库记录，fileBaseinfo不应该为null,taskId={}", dispatchLog.getId());
                fileParser = null;
            }
        }

        if (fileParser == null) {
            //模板解析类不存在
            dispatchLogService.updateEtlStateById(DispatchLogPO.StateEnum.ETL_STATE_PARSER_NOT_FOUND, id);
        } else {

            //任务信息
            if (fileBaseinfo == null) {
                fileBaseinfo = fileBaseinfoService.getById(dispatchLog.getDataResourceTypeId());
            }

            //用于解析过程参数传递
            Map<Object, Object> params = fileParser.initParams(dispatchLog, fileBaseinfo);

            //生成带日期的路径
            String datePath = genPathWithDate(path, dispatchLog.getDispatchDate());

            //获取文件名
            String fileName = fileParser.generateFileName(dispatchLog, params);

            if (fileName != null && HANDLING_FILE.addIfAbsent(datePath + fileName)) {
                //没有线程解析此文件就异步执行
//                threadPoolExecutor.execute(() -> {
                DispatchLogService dispatchLogService = SpringContext.getBean(DispatchLogService.class);
                try {

                    //调用文件解析起进行解析入库操作，并返回执行结果
                    ParseResult parseResult = fileParser.parse(datePath, fileName, dispatchLog, params);

                    /**
                     * 更新任务执行结果
                     * 1.如果关联任务(包含当前任务)有数据则按关联任务更新
                     * 2.如果当前任务属性不为null，则按当前任务属性更新
                     * 3.以上两种条件都不满足，则默认更新任务状态和任务执行时长
                     */
                    if (parseResult.getStatus() != DispatchLogPO.StateEnum.ETL_STATE_INGORE) {
                        if (parseResult.getRelationTasks() != null && !parseResult.getRelationTasks().isEmpty()) {
                            //批量插入方法有bug，未更新数据而结果返回成功
                            //boolean saveOrUpdateBatchStatus = dispatchLogService.saveOrUpdateBatch(parseResult.getRelationTasks());
                            parseResult.getRelationTasks().forEach(relationTask -> {
                                dispatchLogService.updateById(relationTask);
                            });
                        } else if (parseResult.getOtherTask() != null) {
                            dispatchLogService.updateById(parseResult.getOtherTask());
                        } else {
                            long dispathTime = System.currentTimeMillis() - (Long) params.get(TASK_START_TIME_KEY);
                            dispatchLogService.updateEtlStateById(parseResult.getStatus(), id, dispathTime);
                        }
                    }
                } catch (Throwable e) {
                    log.error("数据文件解析异常path={},dispatchLog={}", path, dispatchLog, e);
                    dispatchLogService.updateEtlStateById(DispatchLogPO.StateEnum.ETL_STATE_RUN_FAILURE, id);
                }
//                });
            }
        }
    }

    /**
     * 判断是否要开始执行任务
     *
     * @return
     */
    private boolean isStartTask() {

        String[] tempArr = beginTime.trim().split(":");
        int beginHour = Integer.valueOf(tempArr[0]);
        int beginMinute = Integer.valueOf(tempArr[1]);
        LocalTime now = LocalTime.now();
        int currentHour = now.getHour();
        int currentMinute = now.getMinute();

        boolean isStartTask = currentHour > beginHour || (currentHour == beginHour && currentMinute > beginMinute);
        return isStartTask;
    }

    @Override
    public void destroy() throws Exception {
        threadPoolExecutor.shutdown();
    }

    /**
     * @param path
     * @param date
     * @return 生成带日期的文件路径
     */
    private String genPathWithDate(String path, Date date) {
        String datePath = DateFormatUtils.format(date, "yyyy/MM/dd");
        return path.replace("${date}", datePath);
    }

}
